package com.design.proxy.gpproxy.proxy;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
 * 手写代理实现
 * @author:         cong zhi
 * @createDate:     2022/5/14 18:08
 * @updateUser:     cong zhi
 * @updateDate:     2022/5/14 18:08
 * @updateRemark:   修改内容
 * @version:        v1.0
 */
public class GPProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(GPClassLoader loader, Class<?>[] interfaces, GPInvocationHandler handler) {
        // 1、动态生成源码 .jave文件
        String str = generateSrc(interfaces);
//        System.out.println(str);
        // 2、java 文件输出到磁盘
        //，保存为文件$Proxy0.java
        String filePath = GPProxy.class.getResource("").getPath();
        File file = new File(filePath + "$Proxy0.java");
        try {
            FileWriter fw = new FileWriter(file);
            fw.write(str);
            fw.flush();
            fw.close();

            // 3、把.java文件编译成$Proxy0.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> iterable = manager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();

            // 4、把生成的.class文件加载到JVM中
            Class<?> proxyClass = loader.findClass("$Proxy0");
            Constructor<?> c = proxyClass.getConstructor(GPInvocationHandler.class);
            file.delete();
            // 5、返回新的代理对象
            return c.newInstance(handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuilder sb = new StringBuilder();
        sb.append(GPProxy.class.getPackage() + ";" + ln);
        sb.append("import " + interfaces[0].getName() + ";" + ln);
        sb.append("import com.design.proxy.gpproxy.proxy.GPInvocationHandler;" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public final class $Proxy0 implements " + interfaces[0].getName() + " {" + ln);
        sb.append("GPInvocationHandler h;" + ln);
        sb.append("public $Proxy0(GPInvocationHandler h) {" + ln);
        sb.append("this.h=h;" + ln);
        sb.append("}" + ln);

        for (Method method : interfaces[0].getMethods()) {
            Class<?>[] params = method.getParameterTypes();
            StringBuilder paramNames = new StringBuilder();
            StringBuilder paramValues = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();

            for (int i = 0; i < params.length; i++) {
                Class<?> clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i > 0 && i < params.length - 1) {
                    paramNames.append(",");
                    paramClasses.append(",");
                    paramValues.append(",");
                }
            }
            sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "(" + paramNames.toString() + "){" + ln);
            sb.append("try{");
            sb.append("Method method=" + interfaces[0].getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
            sb.append((hasReturnValue(method.getReturnType()) ? "return" : "") + getCaseCode("this.h.invoke(this,method,new Object[]{" + paramValues + "})", method.getReturnType()) + ";" + ln);
            sb.append("}" + ln);
            sb.append("catch(Error ex){}" + ln);
            sb.append("catch(Throwable throwable)\n" + ln);
            sb.append("{");
            sb.append(" throw  new UndeclaredThrowableException(throwable);\n" + ln);
            sb.append("}");
            sb.append(getReturnEmptyCode(method.getReturnType()));
            sb.append("}");
        }

        sb.append("}" + ln);
        return sb.toString();
    }

    private static String getCaseCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static Map<Class, Class> mappings = new HashMap<>();

    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass) {

        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null";
        }
    }

    private static String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}
